<template>
	<div id="app">
		<div class="tile" 
			v-for="item in list" 
			:style="{
				left: coords[item.r][item.c].c + 'px',
				top: coords[item.r][item.c].r + 'px',
				width: width + 'px',
				height: height + 'px',
				fontSize: fontSize + 'px'
			}"
			:class="['tile-' + item.number, item.fresh ? 'new' : '']" 
			:key="item.id"
			@webkitTransitionEnd="transitionEnd" 
			>
			{{item.number}}
		</div>
		<!-- 控制 -->
		<div class="control">
			<div>
				<span>{{row}}行</span>
				<button @click="adjustRow(-1)"> - </button>	
				<button @click="adjustRow(1)"> + </button>	
			</div>
			<div>
				<span>{{column}}列</span>
				<button @click="adjustColumn(-1)"> - </button>	
				<button @click="adjustColumn(1)"> + </button>	
			</div>
			<button @click="handleStartAuto">
				{{isAuto ? '停止' : '自动'}}
			</button>
		</div>
	</div>
</template>

<script>

import {flat, random} from './utils'
import Hammer from 'hammerjs'

const DIR = {
	L: 'left',
	R: 'right',
	U: 'up',
	D: 'down'
}
const GOAL = 2048
let hammer = null

export default {
	name: 'App',
	data () { 
		return {
			size: 0,
			row: 4, // y
			column: 4, // x
			coords: [],
			tiles: [],
			width: 0,
			height: 0,

			inited: false,
			moving: false,
			isAuto: false
		}
	},
	computed: {
		flatList() {
			return flat(this.tiles)
		},
		restList() {
			const ret = []
			this.tiles.forEach((rows, rowIndex) => {
				rows.forEach((tile, columnIndex) => {
					if (tile === null) {
						ret.push([rowIndex, columnIndex])
					}
				})
			})
			return ret
		},
		list () {
			return this.flatList.filter(item => item !== null)
		},
		fontSize () {
			const width = this.width
			const height = this.height
			const min = width > height ? height : width
			return Math.round(min / 3)
		},
	},
	mounted() {
		this.init()
		this.handleBind()
		this.genNewTile()
		window.addEventListener('resize', this.handleResize)
	},
	beforeDestroy() {
		window.removeEventListener('resize', this.handleResize)
	},
	methods: {
		adjustRow (step) {
			if (this.row <= 2 || this.isAuto) {
				return
			}
			this.row += step
			this.calcTileSizeAndPos()
			const len = this.tiles.length
			if (len > this.row) {
				this.tiles = this.tiles.slice(0, this.row)
			} else {
				const tiles = [...this.tiles]
				tiles[this.row - 1] = new Array(this.column).fill(null)
				this.tiles = tiles
			}
		},
		adjustColumn (step) {
			if (this.column <= 2 || this.isAuto) {
				return
			}
			this.column += step
			this.calcTileSizeAndPos()
			const len = this.tiles[0].length
			if (len > this.column) {
				const ret = []
				this.tiles.forEach(rows => {
					ret.push(rows.slice(0, this.column))
				})
				this.tiles = ret
			} else {
				const tiles = [...this.tiles]
				tiles.forEach(rows => {
					rows[this.column - 1] = null
				})
				this.tiles = tiles
			}
		},
		handleStartAuto () {
			this.isAuto = !this.isAuto
			if (this.isAuto) {
				this.auto()
			}
		},
		handleBind () {
			const box = document.querySelector('#app')
			hammer = new Hammer(box)
			hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL })
			hammer.on('swipeleft', () => this.touchMove(DIR.L))
			hammer.on('swiperight', () => this.touchMove(DIR.R))
			hammer.on('swipeup', () => this.touchMove(DIR.U))
			hammer.on('swipedown', () => this.touchMove(DIR.D))
		},
		calcTileSizeAndPos() {
			const {width} = document.querySelector('#app').getBoundingClientRect()
			this.size = width
			this.width = Math.floor(width / this.column)
			this.height = Math.floor(width / this.row)
			for (let rowIndex = 0; rowIndex < this.row; rowIndex++) {
				for (let columnIndex = 0; columnIndex < this.column; columnIndex++) {
					const rows = this.coords[rowIndex] || (this.coords[rowIndex] = [])
					rows[columnIndex] = { 
						c: Math.floor(this.size * columnIndex / this.column), 
						r: Math.floor(this.size * rowIndex / this.row)
					}
				}
			}
		},
		initRow () {
			for (let rowIndex = 0; rowIndex < this.row; rowIndex++) {
				this.tiles[rowIndex] = new Array(this.column).fill(null)
			}
		},
		handleResize () {
			this.calcTileSizeAndPos()
		},
		
		init () {
			this.calcTileSizeAndPos()
			this.initRow()
		},
		nextTarget(rowIndex, columnIndex, direction, tiles) {
			const [r, c] = this.getNextStep(direction)
			rowIndex+= r
			columnIndex+= c
			while (tiles[rowIndex][columnIndex] === null && this.hasReachedEnd(rowIndex, columnIndex, direction)) {
				const [r, c] = this.getNextStep(direction)
				rowIndex+= r
				columnIndex+=c
			}
			return [rowIndex, columnIndex, tiles[rowIndex][columnIndex]]
		},
		hasReachedEnd(rowIndex, columnIndex, direction) {
			switch (direction) {
				case DIR.L: return columnIndex > 0
				case DIR.R: return columnIndex < this.column - 1
				case DIR.U: return rowIndex > 0
				case DIR.D: return rowIndex < this.row - 1
			}
		},
		getNextStep(direction) {
			switch (direction) {
				case DIR.L: return [0, -1]
				case DIR.R: return [0, 1]
				case DIR.U: return [-1, 0]
				case DIR.D: return [1, 0]
			}
		},
		getNextGridIndex (rowIndex, columnIndex, direction) {
			const forward = direction === DIR.L || direction === DIR.U
			const rowMax = this.row - 1
			const columnMax = this.column - 1
			if (rowIndex === null) {
				return forward ? [0, 0, true] : [rowMax, columnMax, true]
			}
			let isCurrentValid = true
			if (forward) {
				if (rowIndex < rowMax) {
					if (columnIndex < columnMax) {
						columnIndex += 1
					} else {
						rowIndex += 1
						columnIndex = 0
					}
				} else {
					columnIndex += 1
					if (columnIndex > columnMax) {
						isCurrentValid = false
					}
				}
			} else {
				if (rowIndex > 0) {
					if (columnIndex > 0) {
						columnIndex -= 1
					} else {
						rowIndex -= 1
						columnIndex = columnMax
					}
				} else {
					columnIndex -= 1
					if (columnIndex < 0) {
						isCurrentValid = false
					}
				}
			}
			return [rowIndex, columnIndex, isCurrentValid]
		},	
		getPrevStep(direction) {
			switch (direction) {
				case DIR.L: return [0, 1]
				case DIR.R: return [0, -1]
				case DIR.U: return [1, 0]
				case DIR.D: return [-1, 0]
			}
		},
		touchMove(direction) {
			if (this.gameOverOrNot()) {
				return this.handleGameOver()
			}
			const tiles = [...this.tiles]
			if (this.moving) { return }
			this.moving = true
			let needMove = false
			// 行、 列， 是否继续
			let [rowIndex , columnIndex, isCurrentValid] = this.getNextGridIndex(null, null, direction)
			while (isCurrentValid) {
				const tile = tiles[rowIndex][columnIndex] // 
				if (tile !== null) {
					if (this.hasReachedEnd(rowIndex, columnIndex, direction)) {
						const [targetR, targetC, target] = this.nextTarget(rowIndex, columnIndex, direction, tiles)
						if (target === null) {
							needMove = true
							tile.c = targetC
							tile.r = targetR
						} else {
							if (target.number === tile.number && !target.upgrade) { // 合并
								needMove = true
								tile.c = target.c
								tile.r = target.r
								tile.zIndex = target.zIndex + 1
								tile._number = tile.number * 2
								tile.upgrade = true
								target.dead = true
							} else {
								const [tr, tc] = this.getPrevStep(direction)
								const nextC = target.c + tc
								const nextR = target.r + tr
								if (nextC !== tile.c || nextR !== tile.r) {
									needMove = true
									tile.c = nextC
									tile.r = nextR
								}
								
							}
						}
					}
				}
				const nextStepInfo = this.getNextGridIndex(rowIndex, columnIndex, direction)
				rowIndex = nextStepInfo[0] // 第几行
				columnIndex =  nextStepInfo[1] // 第几列
				isCurrentValid = nextStepInfo[2]
			}
			this.tiles = tiles
			if (!needMove) {
				this.moving = false
				setTimeout(this.genNewTile, 100)
			}
		},
		transitionEnd () {
			if (!this.moving) {
				return
			}
			const tiles = [...this.tiles]
			const ret = tiles.map(item => new Array(item.length).fill(null))
			for (let rowIndex = 0; rowIndex < this.row; rowIndex++) {
				for (let columnIndex = 0; columnIndex < this.column; columnIndex++) {
					const tile = tiles[rowIndex][columnIndex]
					if (tile === null || tile.dead) {
						continue
					}
					
					tile.fresh = false
					if (tile.upgrade) {
						tile.number = tile._number
						tile.upgrade = false

					}
					ret[tile.r][tile.c] = tile
				}
			}
			this.tiles = ret
			this.moving = false

			setTimeout(this.genNewTile, 100)
		},
		
		auto() {
			// console.log('hammer', hammer)
			try {
				const methords = [hammer.handlers.swipedown[0],
				hammer.handlers.swipeup[0],
				hammer.handlers.swipeleft[0],
				hammer.handlers.swiperight[0]]
				const fn = methords[random(methords.length - 1)]
				fn()
			} catch (e) {
				console.log('e', e)
			}
		},

		hasMergingTile() {
			let shouldGameOver = true
			let [rowIndex , columnIndex, isCurrentValid] = this.getNextGridIndex(null, null, DIR.L)
			while (isCurrentValid) {
				// console.log(1)
				const tile = this.tiles[rowIndex][columnIndex]
				const preTile = columnIndex >= this.column - 1 ? null :  this.tiles[rowIndex][columnIndex + 1]
				if (preTile && tile.number === preTile.number) {
					shouldGameOver = false
					break
				}
				const nextTile = rowIndex >= this.row - 1 ? null :  this.tiles[rowIndex + 1][columnIndex]
				if (nextTile && tile.number === nextTile.number) {
					shouldGameOver = false
					break
				}
				const nextStepInfo = this.getNextGridIndex(rowIndex, columnIndex, DIR.L)
				rowIndex = nextStepInfo[0] // 第几行
				columnIndex =  nextStepInfo[1] // 第几列
				isCurrentValid = nextStepInfo[2]
			}
			return shouldGameOver
		},
		// 达到目标
		reached () {
			const tiles = this.tiles
			for (let rowIndex = 0; rowIndex < this.row; rowIndex++) {
				for (let columnIndex = 0; columnIndex < this.column; columnIndex++) {
					if (tiles[rowIndex] && tiles[rowIndex][columnIndex] && tiles[rowIndex][columnIndex].number === GOAL) {
						return true
					}
				}
			}
			return false
		},
		gameOverOrNot () {
			const restLen = this.restList.length
			if (restLen === 0 && this.hasMergingTile()) {
				return true
			}
			return this.reached()
		},
		genNewTile () {
			if (this.gameOverOrNot()) {
				return this.handleGameOver()
			}
			const rest = [...this.restList]
			if (rest.length) {
				let len = rest.length
				let num = 1
				if (!this.inited) {
					this.inited = true
					num = 2
				}
				const tiles = this.tiles.map(item => item)
				while (num > 0) {
					num--
					const index = random(len - 1)
					const [rowIndex, columnIndex] = rest[index]
					rest.splice(index, 1)
					len = rest.length
					tiles[rowIndex][columnIndex] = {
						number: 2,
						id: `${Date.now()}_${columnIndex}_${rowIndex}`,
						c: columnIndex,
						r: rowIndex,
						zIndex: 0,
						upgrade: false,
						dead: false,
						fresh: true
					}
				}
				this.tiles = tiles
			}
			if (this.isAuto) {
				setTimeout(this.auto, 100)
			}
		},
		reset() {
			this.coords =  []
			this.tiles = []
			this.inited = false
			this.moving = false
		},
		handleGameOver() {
			this.isAuto = false
			alert('gameover')

			if (confirm('是否继续')) {
				this.reset()
				this.init()
				this.genNewTile()
			}
		}
	}
}
</script>

<style>
@import url(./style.css);
</style>
